package network;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;

public class Session implements ISession {

    protected static Session instance = new Session();

    public static Session gI() {
        return instance;
    }
    private DataOutputStream dos;
    public DataInputStream dis;
    public IMessageHandler messageHandler;
    private SocketConnection sc;
    public boolean connected, connecting;
    
    private final Sender sender = new Sender();
    
    public Thread initThread;
    public Thread collectorThread;
   // public Thread sendThread;
    
    public int sendByteCount;
    public int recvByteCount;
    boolean getKeyComplete;
    public byte[] key = null;
    private byte curR, curW;
    long timeConnected;
    public String strRecvByteCount = "";
    public static boolean isCancel;
    private final static int maxRetry = 15;
    
    public boolean isConnected() {
        return connected;
    }

    public void setHandler(IMessageHandler messageHandler) {
        this.messageHandler = messageHandler;
    }

    public void connect(String host) {
        if(connected)
            return;
        getKeyComplete = false;
        sc = null;
        initThread = new Thread(new NetworkInit(host));
        initThread.start();

    }//
    
    public void sendMessage(Message message) {
        sender.AddMessage(message);
    }

    private synchronized void doSendMessage(Message m) throws IOException {
        byte[] data = m.getData();
        try {
            if (getKeyComplete) {
                byte b = (writeKey(m.command));
                dos.writeByte(b);
            } else {
                dos.writeByte(m.command);
            }
            if (data != null) {
                int size = data.length;
                if (getKeyComplete) {
                    int byte1 = writeKey((byte) (size >> 8));
                    dos.writeByte(byte1);
                    int byte2 = writeKey((byte) (size & 0xFF));
                    dos.writeByte(byte2);
                } else {
                    dos.writeShort(size);
                }
                //
                if (getKeyComplete) {
                    int a = data.length;
                    for (int i = 0; i < a; i++) {
                        data[i] = writeKey(data[i]);
                    }
                }
                dos.write(data);
                sendByteCount += (5 + data.length);
            } else {
                dos.writeShort(0);
                sendByteCount += 5;
            }

            System.out.println("send " + m.command);
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private byte readKey(byte b) {
        byte i = (byte) ((key[curR++] & 0xff) ^ (b & 0xff));
        if (curR >= key.length) {
            curR %= key.length;
        }
        return i;
    }

    private byte writeKey(byte b) {
        byte i = (byte) ((key[curW++] & 0xff) ^ (b & 0xff));
        if (curW >= key.length) {
            curW %= key.length;
        }
        return i;
    }
    public void close() {
        cleanNetwork();
    }

    private void cleanNetwork() {
        key = null;
        curR = 0;
        curW = 0;
        try {
            connected = false;
            connecting = false;
            if (sc != null) {
                sc.close();
                sc = null;
            }
            if (dos != null) {
                dos.close();
                dos = null;
            }
            if (dis != null) {
                dis.close();
                dis = null;
            }
           // sendThread = null;
            collectorThread = null;
           // System.gc();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void connect(String iP, int pORT) {
        // TODO Auto-generated method stub
        String host = "socket://" + iP + ":" + pORT;
        connect(host);
    }

    class NetworkInit implements Runnable {

        private final String host;

        NetworkInit(String host) {
            this.host = host;
        }

        public void run() {
            isCancel = false;
            // time out thread start
            int i = 0;
            while (i < maxRetry && !connected) {
                try {
                    doConnect(host);
                    messageHandler.onConnectOK();
                    break;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    System.out.println("Retry connection " + i);
                    i++;
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException ex1) {
                        ex1.printStackTrace();
                    }
                }
            }

            if (!connected && messageHandler != null) {
                close();
                messageHandler.onConnectionFail();
            }
        }

        public void doConnect(String host) throws Exception {
            sc = (SocketConnection) Connector.open(host);
            dos = sc.openDataOutputStream();
            dis = sc.openDataInputStream();
            connected = true;
            new Thread(sender).start();
            collectorThread = new Thread(new MessageCollector());
            collectorThread.start();
            timeConnected = System.currentTimeMillis();
            doSendMessage(new Message((byte) -27));
            connecting = false;
            // messageHandler.onConnectOK(); MOVE TO GET KEY
        }
    }
    
    
    private class Sender implements Runnable {

        private final Vector sendingMessage;

        public Sender() {
            sendingMessage = new Vector();
        }

        public void AddMessage(Message message) {
            sendingMessage.addElement(message);
        }

        public void run() {
            try {
                while (connected) {
                    if (getKeyComplete) {
                        while (sendingMessage.size() > 0) {
                            Message m = (Message) sendingMessage.elementAt(0);
                            sendingMessage.removeElementAt(0);
                            doSendMessage(m);
                        }
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    class MessageCollector implements Runnable {

        public void run() {
            network.Message message;
            try {
                while (isConnected()) {
                    message = readMessage();
                    if (message != null) {
                        try {
                            if (message.command == -27) {
                                getKey(message);
                            } else {
                                messageHandler.onMessage(message);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } else {
                        break;
                    }
                }
            } catch (Exception ex) {
            }

            if (connected) {
                if (messageHandler != null) {
                    if (System.currentTimeMillis() - timeConnected > 500) {
                        messageHandler.onDisconnected();
                    } else {
                        messageHandler.onConnectionFail();
                    }
                }
                if (sc != null) {
                    cleanNetwork();
                }
            }
        }

        private void getKey(Message message) throws IOException {
            byte keySize = message.reader().readByte();
            key = new byte[keySize];
            for (int i = 0; i < keySize; i++) {
                key[i] = message.reader().readByte();
            }
            int a = key.length - 1;
            for (int i = 0; i < a; i++) {
                key[i + 1] ^= key[i];
            }
            getKeyComplete = true;
        }

        private Message readMessage() throws Exception {
            // read message command
            byte cmd = dis.readByte();
            if (getKeyComplete) {
                cmd = readKey(cmd);
            }
            // read size of data
            int size;
            if (getKeyComplete) {
                byte b1 = dis.readByte();
                byte b2 = dis.readByte();
                size = (readKey(b1) & 0xff) << 8 | readKey(b2) & 0xff;
            } else {
                size = dis.readUnsignedShort();
            }
            byte data[] = new byte[size];
            int len = 0;
            int byteRead = 0;
            while (len != -1 && byteRead < size) {
                len = dis.read(data, byteRead, size - byteRead);
                if (len > 0) {
                    byteRead += len;
                }
            }
            recvByteCount += (5 + byteRead);
            int Kb = (Session.gI().recvByteCount + Session.gI().sendByteCount);
            strRecvByteCount = Kb / 1024 + "." + Kb % 1024 / 1024 + "Kb";
            if (getKeyComplete) {
                int a = data.length;
                for (int i = 0; i < a; i++) {
                    data[i] = readKey(data[i]);
                }
            }
            Message msg = new Message(cmd, data);
            System.out.println("read " + cmd);
            return msg;
        }
    }

    
}
